package triangle.reader;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;

/**
 * This file is part of Rodney Ryan's submission of
 * his solution to the "Triangle Puzzle".
 * <p/>
 * This class implements a ReadingStrategy that reads the
 * file from back to front, bit by bit. For large files.
 *
 * rodney@rodneyryan.com
 */
public class PieceByPieceReadingStrategy implements ReadingStrategy {

    private static final int DEFAULT_BUFFER_SIZE = 8192;

    private RandomAccessFile randomAccessFile;
    private boolean isOpen;

    private String fileName;

    private long lastReadPosition;
    private byte[] buffer;
    private int bufferSize;
    private int bufferIndex;

    public PieceByPieceReadingStrategy() {
        this(DEFAULT_BUFFER_SIZE);
    }

    public PieceByPieceReadingStrategy(int bufferSize) {
        super();
        this.bufferSize = bufferSize;

        isOpen = false;
        fileName = "(no file is open)";
    }

    @Override
    public void open(File file) {
        try {
            randomAccessFile = new RandomAccessFile(file, "r");
            lastReadPosition = randomAccessFile.length();
            buffer = new byte[bufferSize];
            bufferIndex = -1;
            fileName = file.getName();
            isOpen = true;

        } catch (IOException e) {
            System.out.println("Error opening file \"" + file + "\".");
            e.printStackTrace();

            try {
                randomAccessFile.close();
            } catch (IOException ignoreIt) {
                // ignoreIt
            }

            randomAccessFile = null;
            isOpen = false;
        }
    }

    private int readNextByte() {
        if (bufferIndex != -1) {
            return buffer[bufferIndex--];

        } else {
            try {
                if (lastReadPosition > bufferSize) {
                    lastReadPosition = lastReadPosition - bufferSize;
                    randomAccessFile.seek(lastReadPosition);
                    bufferIndex = randomAccessFile.read(buffer) - 1;
                    return buffer[bufferIndex--];

                } else if (lastReadPosition != 0) {
                    int bytesRemaining = (int) lastReadPosition;
                    lastReadPosition = 0;
                    randomAccessFile.seek(0);
                    bufferIndex = randomAccessFile.read(buffer, 0, bytesRemaining) - 1;
                    return buffer[bufferIndex--];

                } else {
                    return -1;
                }

            } catch (IOException e) {
                System.out.println("Error reading file \"" + fileName + "\".");
                e.printStackTrace();

                try {
                    randomAccessFile.close();
                } catch (IOException ignoreIt) {
                    // ignoreIt
                }

                return -1;
            }
        }
    }

    @Override
    public String readPreviousLine() {
        if (isOpen) {
            int nextByte = readNextByte();
            while ((nextByte == -1) || (nextByte == 0xA) || (nextByte == 0xD)) {
                if (nextByte == -1) {
                    return null;
                }
                nextByte = readNextByte();
            }

            StringBuilder stringBuilder = new StringBuilder();
            while (nextByte != -1 && nextByte != 0xA && nextByte != 0xD) {
                stringBuilder.append((char) nextByte);
                nextByte = readNextByte();
            }

            String line = stringBuilder.reverse().toString().trim();
            if (line.length() == 0) {
                return readPreviousLine();
            } else {
                return line;
            }

        } else {
            throw new RuntimeException(("No file is open."));
        }
    }

    @Override
    public void close() {
        isOpen = false;
        buffer = null;
        try {
            randomAccessFile.close();
        } catch (IOException e) {
            System.out.println("Error closing file \"" + fileName + "\".");
            e.printStackTrace();
        }
    }
}
